/** @file         led_drv.c
 *  @brief        led 平台驱动文件
 *  @details      基于驱动模块的方式实现 驱动注册/注销等等操作。
 *  @author       lzm
 *  @date         2021-03-30 20:22:03
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/


#include "led_pdrv.h"

/* 
 * 步骤注释声明
 * [module]：模块框架
 * [bus_dev]：总线设备框架。
 * [bus_drv]：总线驱动框架。
 * [drv]：实现驱动内容。包括内核设备文件、设备节点等等。
 * 总线设备框架和总线驱动框架都是属于内核的架子，和模块框架一个性质。 
 */

/* [drv] 驱动内容 */
static dev_t led_dev_num_start = 0; // 该驱动的一个开始设备号，用于占用
static struct class *led_dev_class; // 设备类


/* [drv][1] */
/* 实现驱动操作内容 */
/** @brief  led_dev_open
  * @param 
  * @retval 
  * @author lzm
  */
static int led_dev_open(struct inode *inode, struct file *filp)
{
    unsigned int val = 0;
    led_data_t *cur_led = container_of(inode->i_cdev, led_data_t, led_cdev);

    printk("%s\n", __func__);

    /* 引脚配置 */
    /* 初始化时钟 */
    val = ioread32(cur_led->va_ccm_ccgrx);
    val &= ~(3 << cur_led->clock_offset);
    val |= (3 << cur_led->clock_offset);
    iowrite32(val, cur_led->va_ccm_ccgrx);
    /* 端口复用 */
    iowrite32(0x05, cur_led->va_iomuxc_mux);
    /* 电气属性 */
    iowrite32(0x0001F838, cur_led->va_iomux_pad);
    /* 输出模式 */
    val = ioread32(cur_led->va_gdir);
    val &= ~(1 << cur_led->pin);
    val |= (1 << cur_led->pin);
    iowrite32(val, cur_led->va_gdir);
    /* 输出高电平 */
    val = ioread32(cur_led->va_dr);
    val |= (0x01 << cur_led->pin);
    iowrite32(val, cur_led->va_dr);
    
    cur_led->status = 1;

    /* [drv][1][1] 保存自定义设备结构体 */
    filp->private_data = cur_led; // 用于保存自定义设备结构体的地址。使得其它函数可以调用，如write、read。

    return 0;
}

/** @brief  led_dev_release
  * @param 
  * @retval 
  * @author lzm
  */
static int led_dev_release(struct inode *inode, struct file *filp)
{
    printk("%s\n", __func__);

    return 0;
}

/** @brief  led_dev_write
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t led_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    unsigned long val;
    unsigned long ret;
    int tmp = count;
    led_data_t *cur_led = (led_data_t *)filp->private_data;    

    printk("%s\n", __func__);

    /* 获取控制数据 */
    kstrtoul_from_user(buf, tmp, 10, &ret);

    val = ioread32(cur_led->va_dr);
    if(ret)
    {
        val |= (0x01 << cur_led->pin);
        cur_led->status = 1;
    }
    else
    {
        val &= ~(0x01 << cur_led->pin);
        cur_led->status = 0;
    }
    iowrite32(val, cur_led->va_dr);

    *ppos += tmp; // 一直记着

    return tmp;
}

/** @brief  led_dev_read
  * @param 
  * @retval 
  * @author lzm
  */
static ssize_t led_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned char led_status;
    int ret;
    led_data_t *cur_led = (led_data_t *)(filp->private_data);

    printk("%s\n", __func__);

    led_status = cur_led->status;
    ret = copy_to_user(buf, &led_status, 1);

    return 1;
}

/* [drv][2] */
/* 填充驱动操作结构体 */
/* [file_operations] */
static struct file_operations led_dev_fops = 
{
    .owner = THIS_MODULE,
    .open = led_dev_open,
    .release = led_dev_release,
    .write = led_dev_write,
    .read = led_dev_read,
};

/* [bus_drv][1] */
/* 实现总线驱动内容 */
/** @brief  led_pdrv_probe 匹配成功后的回调函数
  * @param  
  * @retval
  * @author lzm
  */
int led_pdrv_probe(struct platform_device *pdev)
{
    dev_t cur_dev_num; // 当前设备号
    led_data_t *cur_led; // 当前 led 设备
    unsigned int *led_hw_info; // 当前 led 设备 私有资源

	struct resource *mem_ccm_ccgrx; // 端口时钟寄存器 资源
	struct resource *mem_iomuxc_mux; // 端口复用寄存器 资源
	struct resource *mem_iomux_pad; // 电气属性寄存器 资源
	struct resource *mem_gdir; // 输入输出寄存器 资源
    struct resource *mem_dr; // 数据寄存器 资源

    printk("%s\n", __func__);

    /* 动态申请内存，且关联设备。即是设备注销时，自动释放内存。 */
    cur_led = devm_kzalloc(&pdev->dev, sizeof(led_data_t), GFP_KERNEL);
    if(!cur_led)
    {
        printk("error for [cur_led] [devm_kzalloc]\n");
        return -ENOMEM;
    }
    led_hw_info = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
    if(!led_hw_info)
    {
        printk("error for [led_hw_info] [devm_kzalloc]\n");
        return -ENOMEM;
    }

    /* 获取设备私有数据 */
    led_hw_info = dev_get_platdata(&pdev->dev); // 需要开发者自己知道该私有数据的数据框架。

    /* 填充 led 结构体 */
    cur_led->pin = led_hw_info[0]; // pin 偏移
    cur_led->clock_offset = led_hw_info[1]; // clock 偏移

    /* 从平台设备中获取 resource */
    /* 注意顺序 */
    mem_ccm_ccgrx = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    mem_iomuxc_mux = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    mem_iomux_pad = platform_get_resource(pdev, IORESOURCE_MEM, 2);
    mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 3);
    mem_dr = platform_get_resource(pdev, IORESOURCE_MEM, 4);

    /* 继续填充 led 结构体 */
    /* MMU 获取虚拟地址，并赋值给 led 结构体管理 */
    cur_led->va_ccm_ccgrx = devm_ioremap(&pdev->dev, mem_ccm_ccgrx->start, resource_size(mem_ccm_ccgrx));
    cur_led->va_iomuxc_mux = devm_ioremap(&pdev->dev, mem_iomuxc_mux->start, resource_size(mem_iomuxc_mux));
    cur_led->va_iomux_pad = devm_ioremap(&pdev->dev, mem_iomux_pad->start, resource_size(mem_iomux_pad));
    cur_led->va_gdir = devm_ioremap(&pdev->dev, mem_gdir->start, resource_size(mem_gdir));
    cur_led->va_dr = devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr));

    /* 以上已经获取完所需资源 */

    /* 编写驱动内容 */
    /* [drv][3][1] 申请设备号 */
    cur_dev_num = MKDEV(MAJOR(led_dev_num_start), pdev->id);
    register_chrdev_region(cur_dev_num, 1, LED_DEV_NAME);
    /* [drv][5] 初始化设备内核文件 */
    cdev_init(&cur_led->led_cdev, &led_dev_fops);
    /* [drv][6] 把设备内核文件注册到内核 */
    cdev_add(&cur_led->led_cdev, cur_dev_num, 1);
    /* [drv][7] 创建设备节点 */
    device_create(led_dev_class, NULL, cur_dev_num, NULL, LED_DEV_INODE_NAME"%d", pdev->id);

    /* 继续填充 led 结构体 */
    cur_led->dev_num = cur_dev_num;

    /* [bus_drv][1][1] 把 LED 数据存入平台设备结构体中 */
    platform_set_drvdata(pdev, cur_led); // pdev->dev->driver_data = cur_led
    
    return 0;
}

/** @brief  led_pdrv_remove 对应的设备被注销或者本驱动被注销时调用该函数
  * @param  
  * @retval
  * @author lzm
  */
int led_pdrv_remove(struct platform_device *pdev)
{
    dev_t cur_dev_num;
    led_data_t *cur_led = platform_get_drvdata(pdev);

    printk("%s\n", __func__);

    /* 获取当前 led 的设备号 */
    cur_dev_num = cur_led->dev_num;

    /* [drv][8] 删除设备节点 */
    device_destroy(led_dev_class, cur_dev_num);
    /* [drv][9] 删除设备内核文件 */
    cdev_del(&cur_led->led_cdev);
    /* [drv][10] 归还设备号 */
    unregister_chrdev_region(cur_dev_num, 1);

    return 0;
}


/* 匹配 兼容 */
static struct platform_device_id led_pdev_ids[] =
{
    {.name = "led_pdev"},
    {}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);


/* [bus_drv][2] */
/* 填充总线驱动结构体（平台驱动） */
static struct platform_driver led_pdrv =
{
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver.name = "led_pdev", // 用于匹配 *
    .id_table = led_pdev_ids, // 用于匹配 **
};


/* [bus_drv][3] */
/* 实现属性文件内容 */
static char *pdrv_name = "led_pdev";
/** @brief  led_drv_attr_show
  * @param  
  * @retval 
  * @author lzm
  */
ssize_t led_drv_attr_show(struct device_driver *drv, char *buf)
{
    return sprintf(buf, "%s\n", pdrv_name);
}
DRIVER_ATTR_RO(led_drv_attr); // 只读文件 led_drv_attr

/* [module][1] */
/* 模块入口函数 */
static __init int led_pdrv_init(void)
{
    printk("%s\n", __func__);

    /* [drv][3] 申请设备号 */
    alloc_chrdev_region(&led_dev_num_start, 0, 1, LED_DEV_NAME); // 占用主设备号

    /* [drv][4] 创建设备类 */
    led_dev_class = class_create(THIS_MODULE, LED_DEV_CLASS);

    /* [bus_drv][4] 注册平台驱动 */
    platform_driver_register(&led_pdrv);
    
    /* [bus_drv][5] 创建平台驱动属性文件 */
    driver_create_file(&led_pdrv.driver, &driver_attr_led_drv_attr);

    return 0;
}
module_init(led_pdrv_init);

/* [module][2] */
/* 模块出口函数 */
static __exit void led_pdrv_exit(void)
{
    printk("%s\n", __func__);

    /* [bus_drv][6] 删除平台驱动属性文件 */
    driver_remove_file(&led_pdrv.driver, &driver_attr_led_drv_attr);
    
    /* [bus_drv][7] 注销平台驱 */
    platform_driver_unregister(&led_pdrv);

    /* [drv][11] 归还占用设备号 */
    unregister_chrdev_region(led_dev_num_start, 1);
    /* [drv][12] 删除设备类 */
    class_destroy(led_dev_class); 
}
module_exit(led_pdrv_exit);

/* [module][3] */
/* 协议 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lizhuming");
MODULE_DESCRIPTION("this is a example for led paltform driver!");
